In [ ]:
## future import 
from __future__ import division, print_function

5. Getting stuff done with Python


In this unit, we are going to learn a new data structure with richer features compared to lists. The problem with lists, while flexible enough to store different data types in one variable, is that it does not provide us a way to "label" that information with meta data. For example in a list ['Andy', 28, 980.15], we would like to be able to associate with the value Andy a label or key 'name' so that we don't have to keep on recalling that a staff's name is located in the first position of the list.

Secondly, we will cover the for and if else construct. Technically speaking, armed with the basic tools that we have learnt from the previous unit, we could go about writing code and basic scripts. But with these keywords, what can achieved is rather limited. Worse, the process of writing code will be tedious and unenlightening. From a scientific and analytic point of view, writing code is not only an ends to obtaining the results we want, but a way for us to structure our thinking and come to an understanding of the problem.

5.1 Learning objectives for this unit

  1. To use the dict data structure and related methods.

  2. To apply boolen conditionals to control if, if else and if elif statements.

  3. To use the for loop to iterate through repetitive calculations.

6. The dictionary


Lists - while easy to create- have the weakness that one cannot easily retrieve data that has been already stored in it. Since the primary means of retrieving information in a list is via indexing and slicing, you need to know the exact integer positions of each data stored in the list. This can (and will often) lead to human errors in programming. Furthermore, an integer based recall is unenlightening. Other people who reads your code will find it difficult to understand what is being written.

To remedy this, Python has built into its base package a data structure called dictionaries. A dictionary is simply a key-value pairing like so $$(key_1, value_1), (key_2, value_2)\ldots, (key_n, value_n)$$ where $key_i$ are usually (but not always) strings and $value_i$ any Python object (int, str, list and even other dict!)

If my_dictionary is a dictionary. Then calling my_dictionary[key_1] will return you the value associated with key_1 in my_dictionary, say value_1.

6.1 Creating dictionaries

Dictionaries are created using curly braces { }. Inside the curly braces, we simply list down all the key-value pairs with a colon :. Different pairs are seperated by a ,.


In [ ]:
# creating a dictionary and assigning it to a variable 

staff = {'name': 'Andy', 'age': 28, 'email': 'andy@company.com' }

In [ ]:
staff['name']

In [ ]:
staff['age']

In [ ]:
print(staff['email'])

In [ ]:
# A dictionary is of class dict 
print(type(staff))

In [ ]:
# list of all keys, note the brackets at the end. 
# .keys is a method associated to dictionaries

staff.keys()

In [ ]:
# list of all values, in no particular order
staff.values()

In [ ]:
# list all key-value pairings using .items

staff.items()

6.2 Updating dictionaries

Very often, we need to change dictionary values and/or add more entries to our dictionary.


In [ ]:
# Hey, Andy mistakenly keyed in his age. He is actually 29 years old! 

staff['age'] = 29
print(staff)

In [ ]:
# HR wants us to record down his staff ID. 

staff['id'] = 12345 
print(staff)

In [ ]:
# Let's check the list of keys
staff.keys()

An implication of this is that we can start with an empty dictionary and add keys as we go along.

And empty dictionary is created by assigning a variable to an instance of class dict by calling dict().


In [ ]:
favourite_food = dict() # You could also type favourite_food = {}
print(favourite_food)

6.2.1 Dictionary exercise

Here's what I want you to do. Go around the room and find out the favourite food of three colleagues here. Then update this empty dictionary with 3 key-value pairings where keys are your colleague's name and value his/her favourite food. Have fun!


In [ ]:
# update your dictionary here 


# and print the dictionary 
print(favourite_food)

6.2.2 Using the .update method

To combine two dictionaries, we use the .update method.


In [ ]:
staff.update({'salary': 980.15, 'department':'finance', 'colleagues': ['George', 'Liz']})

In [ ]:
# Who are Andy's colleagues? Enter answer below

In [ ]:
# Which department does he work in? Enter answer below

6.2.3 Creating dictionaries using kwargs

Dictionaries can also be created by calling dict() with keyword arguments (or kwargs). When we create dictionaries like this, the key value pairs are written as $$ key_1 = value_1, key_2 = value_2, \ldots $$ again each seperated by a comma.


In [ ]:
my_favourite_things = dict(food="Assam Laksa", music="classical", number = 2)

In [ ]:
# my favourite number 

my_favourite_things['number']

Note from the example above that when creating dictionaries, DO NOT enclose the keys within quotation marks. However, when accessing the value of a dictionary by its key, you MUST use quotation marks.


In [ ]:
# An error

my_favourite_things[food]

In [ ]:
# ...but this is correct 

food = 'food'

my_favourite_things[food]

7. If and boolen conditionals


Any algorithm worth its salt will have conditionals in it. Very rarely does an algorithm just proceed in a linear fashion, one step following another step. More often than not, one step will follow from another if some condition is satisfied.

This situation can be programmed in Python by using the if keyword. The general construct of an if statement looks like this:

if condition :

do something

Python will only execute the code block do something only if condition evaluate to True. Otherwise it ignores it. Notice the lack of { } surrounding the code block to be executed. The Python interpreter relies on indentation and newline to "know" which code block is conditioned on.

This enforces good coding style and makes it so nice to program with Python. No more worrying about unmatched braces!

7.1 Conditionals

Since conditionals are logical statements, we need some binary comparison operators

  • == logical equals to
  • > strictly greater than
  • < strictly less than
  • != logical not equals to

We can create compound statements by using the keywords or and and. Their bitwise versions are | and &.


In [ ]:
# examples of binary comparison 

1 < 2

In [ ]:
# compound statements 

1 < 2 or 1 == 2

In [ ]:
# using bitwise operators 

1<2 & 1==2

Here is our first example using the if keyword.


In [ ]:
x = 300

if x == 300:
    print('This is Sparta!')

The variable x has been assigned the value 300. When Python sees the statement if x == 300: Python checks the conditional x==300 and evaluates it. Since x was assigned the value 300, the conditional is indeed True. When this happens, it executes the indented code block below the conditional, in this case print the string 'This is Sparta!'


In [ ]:
y = 13

print(y)
if y % 2 == 0:
    print('This is an even number')

print("I guess it's odd then")

The conditional checks and see whether the remainder of 13 when divided by 2 is 0. (Recall that's what % does.) Since 13 returns 1 remainder when divided by 2, the condition is false. Hence the string 'This is an even number' is not printed.

7.2 if else statements

If you look at the previous example, one can see an obvious problem with this block of code. What if y=14? Then the output would consist of two print statements 'This is an even number' and followed by "I guess it's odd then". This is not desireable. In fact we want to print 'This is an even number' only when y is even and "I guess it's odd then" only when y is odd. To code this into Python, we use the else keyword.

Code blocks under the else keyword are executed when the conditionals evaluate to False.

The format of of if else statements are as below:

if condition:

do something 

else:

do something else

In [ ]:
y = 22 

if y%2 ==0:
    print("{} is an even number".format(y))
else:
    print("{} is an odd number".format(y))

In [ ]:
y = 13 

if y%2 ==0:
    print("{} is an even number".format(y))
else:
    print("{} is an odd number".format(y))

In [ ]:
# Nested if else statements

y = 25 

remainder = y%3

if remainder == 0:
    print("{} is divisible by 3".format(y))
else:
    print("{} is not divisible by 3".format(y))
    if remainder ==1:
        print("But has remainder {}".format(remainder))
    else:
        print("But has remainder {}".format(remainder))

Try re-executing the cell above with various values of y and see the effect on the output.

Nested if else statements make for horrible coding. It makes code hard to read and understand. Surely there must be a better way!

7.3 elif to rescue

We use elif when we need to test conditionals in a sequential manner. A sequence of conditionals is tested, one after another until the first True condition is encountered. Then, the code block corresponding to that conditional is executed and program continues on without checking the other conditionals.

The format of elif statements looks like this:

if $condition_1$:

do code 1

elif $condition_2$:

do code 2

$\vdots$

elif $condition_n$:

do code n

elif chains can be "terminated" either by an elif statement itself or by an else if there is no final conditional to be evaluated.

Let's try to refactor (recode) the nested if else statements above using the elif keyword.


In [ ]:
y=25

remainder = y%3

if remainder == 0:
    div = 'is'
    s = 'Hence'
elif remainder == 1:
    div = 'is not'
    s = 'But'
elif remainder == 2:
    div = 'is not'
    s = 'But'
    
print('{} {} divisible by 3\n{} has remainder {}'.format(y, div, s, remainder))

See how much more elegant this is instead of nested if else statements.

Now let's try another example. This time we use elif to program Python to assign letter grades to student marks on an an exam. Here are the letter grade and their assigned intervals.

Grades Interval
A [80, 100]
B [70, 80)
C [60, 70)
D [50, 60)
E [45, 50)
F [0, 45)

In [ ]:
marks = 78.35

if marks >= 80:
    grade = 'A'
elif marks >= 70:
    grade = 'B'
elif marks >= 60:
    grade = 'C'
elif marks >= 50:
    grade = 'D'
elif marks >= 45:
    grade = 'E'
else:
    grade = 'F'

print('Student obtained %.1f marks in the test and hence is awarded %s for this module' % (marks, grade))

As before, play around with the various values of marks to make sure that elif structure is working as intended. Notice that I phrased the conditional only to check agains a lower bound. This is because Python will only execute the code block corresponding to first True conditional in the elif sequence. Even if subsequent conditionals evaluate to true, their code is not run.

8. Performing repetitive tasks. Using the for loop


Most algorithms consist of repeating a certain calculations or computer tasks in an almost similiar manner. To give a scenario, I could instruct Python to print the names of three staff members to the output. I could go about fulfilling this task by writing the following code

print("Staff member Lisa")
print("Staff member Mark")
print("Staff member Andy")

Clearly this is repetitive and tedious. The for keyword allows us to simplify code by writing a single intruction - here print and looping this over a list consisting of staff names [Lisa, Mark, Andy].

The general format of a for loop is given by the following:

for item_n in list:

    do code substituted with item_n

8.1 How a for loop works

Let's start by doing the above by looping over the index of the list. The way it is usually done in Javascript or C is to declare a "counter variable" i and increment it by one to perform the next loop of the algorithm. We could do something like this in Python.


In [ ]:
staff = ['Lisa', 'Mark', 'Andy']

for i in range(0,3): # range(0,3) is a function that produces a sequence of numbers in the form of a list: [0,1,2]
    print("Staff member "+staff[i])

But Python allows us to write a more readable form of the for loop. So the following is equivalent to the above and is preferred.


In [ ]:
for staff_name in staff:
    print("Staff member "+ staff_name)

Note that the "counter variable" staff_name is actually a variable containing the current item in the list as the loop progresses. I could have used any name I wanted for the variable - I could use i to represent the staff's name. But I chose staff_name for readability purposes. As the variable staff_name runs through each item of staff, the code block print is executed with the current value of staff_name. Once that is done, the variable is updated with the next item in the list and the block is executed once more. This proceeds until the end of the list is reached and the for loop terminates.

8.2 Combining for and if statements

To further control what each iteration of the for loop does, we can combine if statements within for statements to trigger certain instructions when a particular iteration fulfils certain conditions.

If we need to exit a for loop prematurely, we can use the break keyword. This is usually used in conjuction with if. When conditions for break is fulfilled, the for loop terminates immediately. Python will not evaluate for statements for remaining items in the iterating list.


In [ ]:
# A common programming interview task. Print 'foo' if x is divisible by 3 and 'bar' if it is divisible by 5 and 'baz' 
# if x is divisible by both 3 and 5. Do this for numbers 1 to 15. 

for num in range(1,16): # range(1,16) produces a list of numbers started from 1 and ending at 15. 
    if num % 3 == 0 and num % 5 !=0:
        print('%d foo' % (num))
    elif num % 5 == 0 and num % 3 != 0:
        print('%d bar' % (num))
    elif num % 5 == 0 and num % 3 == 0:
        print('%d baz' % (num))
    else:
        print('%d' % (num))

Here's a more mathematical usage of the for statement. Suppose we want to compute the decimal expansion of $\sqrt{2}$ accurate to 3 decimal places.After searching Wikipedia, I came up with this recursive formula $$\begin{align*}a_0 &=1 \\ a_{n+1} &= \frac{a_n}{2}+\frac{1}{a_n}\end{align*}$$ Here's how we could implement this.


In [ ]:
max_iter = 10
a = 1

# Since _ is considered a valid variable name, we can use this to
# "suppress" counting indices. 
for _ in range(0, max_iter): 
    a_next = a/2.0 + 1/a

    if abs(a_next-a) < 1e-4: # You can use engineering format numbers in Python
        print("Required accuracy found! Breaking out of the loop.")
        break  
    
    a = a_next
    
print("Approximation of sqrt(2) is: %.3f" % (a))

8.2.1 Your mission, should you choose to accept it

...is to list down all prime numbers less than 100.

A prime number $p$ is a number which is divisible only by 1 and itself.


In [ ]:
# Answer

Expected output:

2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97


In [ ]: